/*
————————————————————————————————————————————————————————————————————————————————————
* 本文件用于实现程序编译部分
* 设计思路：以父进程实现控制编译模块，子进程实现编译功能
* 编译后分为：
* 1. 编译期间系统调用错误（内部错误）
* 2. 编译失败
* 3. 编译成功
————————————————————————————————————————————————————————————————————————————————————
*/
#pragma once

#include <iostream>
#include <string>

#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>
#include <unistd.h>

#include "../Comm/Log.hpp"
#include "../Comm/Comm.hpp"

namespace ns_complie
{
    class Compiler
    {
    public:
        Compiler() {}
        ~Compiler() {}

    public:
// 开始编译
        static int Compile(const std::string &file) // 这一切的前提都是，该文件存在
        {
    // 1. 创建子进程
            pid_t pid = fork();
            // 创建子进程失败
            if(pid < 0)
            {
                _log.LogMessage(ClassFile, Error, "编译期间fork失败");
                return System_Call_Err;
            }
            // 子进程负责编译
            else if (pid == 0)
            {
                // 重定向stderr，当编译失败，让其将失败信息写到file.err中
                umask(0); // 将权限码置为0，防止系统的默认权限码影响
                int open_fd = open(FileOper::File::Complie_Err(file).c_str(), O_CREAT | O_WRONLY, 0644);
                if(open_fd < 0)
                {
                    _log.LogMessage(ClassFile, Error, "编译期间open file.err失败");
                    return System_Call_Err;
                }

                int ret_dup2 = dup2(open_fd, stderr->_fileno); // 重定向
                if(ret_dup2 < 0)
                {
                    _log.LogMessage(ClassFile, Error, "编译期间重定向失败");
                    return System_Call_Err;
                }

                // 子进程进行程序替换，去编译.cc文件
                int ret_execlp = execlp("g++", 
                                        "g++", 
                                        "-o",
                                        FileOper::File::Exe(file).c_str(),
                                        FileOper::File::Src(file).c_str(),
                                        "-DCOMPILER_ONLINE",
                                        "-std=c++11", 
                                        nullptr); // 结尾一定要有nullptr
                                        // 这里-D选项必须带，因为OJ_server中对代码拼接需要动态定义宏，其中这里的宏仅仅是为了防止报错

                _log.LogMessage(ClassFile, Error, "编译期间子进程程序替换失败");
                exit(System_Call_Err);
            }

    // 父进程负责提供编译服务
            // 1. 回收子进程
            pid_t ret_waitpid = waitpid(pid, nullptr, 0);
            if(ret_waitpid < 0)
            {
                _log.LogMessage(ClassFile, Error, "编译期间父进程回收子进程失败");
                return System_Call_Err;
            }

            // 2. 查看编译是否成功 -》 查看exe文件是否形成
            // 编译成功
            if (FileOper::File::IsFileExist(FileOper::File::Exe(file)))
            {
                _log.LogMessage(ClassFile, Info, "编译成功\n");
                return Success;
            }
            // 编译失败
            else
            {
                _log.LogMessage(ClassFile, Error, "编译失败\n");
                return Compile_Err;
            }
        }
    };
};